﻿//  Copyright © 2009-2010 by Rhy A. Mednick
//  All rights reserved.
//  http://rhyduino.codeplex.com
//  
//  Redistribution and use in source and binary forms, with or without modification, 
//  are permitted provided that the following conditions are met:
//  
//  * Redistributions of source code must retain the above copyright notice, this list 
//    of conditions and the following disclaimer.
//  
//  * Redistributions in binary form must reproduce the above copyright notice, this 
//    list of conditions and the following disclaimer in the documentation and/or other 
//    materials provided with the distribution.
//  
//  * Neither the name of Rhy A. Mednick nor the names of its contributors may be used 
//    to endorse or promote products derived from this software without specific prior 
//    written permission.
//  
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
//  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
//  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
//  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
//  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
//  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
//  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
//  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
//  ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
//  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
//  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
namespace Rhyduino
{
    /// <summary>
    ///   This is a utility class that is used to encode messages as Firmata requests.
    /// </summary>
    public static class FirmataEncoder
    {
        #region Public Methods

        /// <summary>
        ///   Generates a byte array containing the encoded data that represents a request to
        ///   write a value to an analog pin.
        /// </summary>
        /// <param name = "pinNumber">The number of the pin to write to.</param>
        /// <param name = "value">The value to be written.</param>
        /// <returns>The encoded message.</returns>
        /// <remarks>
        ///   No paramater verification is done to determine if the values are within an 
        ///   acceptable range. This is by design.
        /// </remarks>
        public static byte[] BuildAnalogWriteRequest(int pinNumber, int value)
        {
            var data = value.ToTwo7BitBytes();
            return new[]
                       {
                           (byte) ((int) RequestMessageType.AnalogWrite | pinNumber),
                           data[0],
                           data[1],
                       };
        }

        /// <summary>
        ///   Generates a byte array containing the encoded data that represents a request to
        ///   write a value to an analog pin.
        /// </summary>
        /// <param name = "portNumber">The digital port to write to. Port 0 contains pins 2-7 and port 1 for pins 8-13.</param>
        /// <param name = "value">The value to be written.</param>
        /// <returns>The encoded message.</returns>
        /// <remarks>
        ///   No paramater verification is done to determine if the values are within an 
        ///   acceptable range. This is by design.
        /// </remarks>
        public static byte[] BuildDigitalWriteRequest(int portNumber, int value)
        {
            // Port 0 only looks at the last 6 bits of the first byte. Shift our value by 2 so the bits are 
            // in the proper position.
            if (portNumber == 0)
            {
                value <<= 2;
            }

            var dataBytes = value.ToTwo7BitBytes();


            return new[]
                       {
                           // Combine message type and port number 
                           (byte) ((int) RequestMessageType.DigitalWrite | portNumber),
                           dataBytes[0],
                           dataBytes[1]
                       };
        }

        /// <summary>
        ///   Generates a byte array containing the encoded data that represents a request to
        ///   have the target hardware report which Firmata protocol version it is using.
        /// </summary>
        /// <returns>The encoded message.</returns>
        public static byte[] BuildProtocolVersionRequest()
        {
            return new[]
                       {
                           START_SYSEX,
                           (byte) RequestMessageType.ProtocolVersion,
                           END_SYSEX
                       };
        }

        /// <summary>
        ///   Generates a byte array containing the encoded data that represents a request to
        ///   change the monitoring state of an analog pin.
        /// </summary>
        /// <param name = "pinNumber">The analog pin monitoring state to change.</param>
        /// <param name = "enable">Flag indicating the desired monitoring status of the pin. 
        ///   Use true to enable reporting and false to disable it.</param>
        /// <returns>The encoded message.</returns>
        public static byte[] BuildReportAnalogPinRequest(int pinNumber, bool enable)
        {
            return new[]
                       {
                           // Combine message type and pin number
                           (byte) ((int) RequestMessageType.ReportAnalogPin | pinNumber),
                           (byte) (enable ? 1 : 0)
                       };
        }

        /// <summary>
        ///   Generates a byte array containing the encoded data that represents a request to
        ///   change the monitoring state of a digital port.
        /// </summary>
        /// <param name = "port">The digital port to monitor. Port 0 contains pins 0-6 and port 1 contains pins 7-13.</param>
        /// <param name = "enable">Flag indicating the desired status of the pins contained in the 
        ///   port. Use true to enable reporting and false to disable it.</param>
        /// <returns>The encoded message.</returns>
        public static byte[] BuildReportDigitalPortRequest(int port, bool enable)
        {
            return new[]
                       {
                           // Combine message type and pin number
                           (byte) ((int) RequestMessageType.ReportDigitalPort | port),
                           (byte) (enable ? 1 : 0)
                       };
        }

        /// <summary>
        ///   Generates a byte array containing the encoded data that represents a request to
        ///   configure a pin previously configured as a servo pin.
        /// </summary>
        /// <param name = "pinNumber">The number of the pin to configure.</param>
        /// <param name = "minPulse">The minimum pulse value in microseconds.</param>
        /// <param name = "maxPulse">The maximum pulse value in microseconds.</param>
        /// <param name = "angle">The number of degrees that the servo turns per pulse.</param>
        /// <returns>The encoded message.</returns>
        public static byte[] BuildServoConfigRequest(int pinNumber, int minPulse, int maxPulse, int angle)
        {
            var minPulseBytes = minPulse.ToTwo7BitBytes();
            var maxPulseBytes = maxPulse.ToTwo7BitBytes();
            var angleBytes = angle.ToTwo7BitBytes();

            return new[]
                       {
                           START_SYSEX,
                           (byte) RequestMessageType.ServoConfig,
                           (byte) pinNumber,
                           minPulseBytes[0],
                           minPulseBytes[1],
                           maxPulseBytes[0],
                           maxPulseBytes[1],
                           angleBytes[0],
                           angleBytes[1],
                           END_SYSEX
                       };
        }

        /// <summary>
        ///   Generates a byte array containing the encoded data that represents a request to
        ///   move a servo to a specified position.
        /// </summary>
        /// <param name = "pinNumber">The number of the pin.</param>
        /// <param name = "value">The position, in degrees, to move the servo to.</param>
        /// <returns></returns>
        public static byte[] BuildServoPositionRequest(int pinNumber, int value)
        {
            // The servo position request is really just an analog write message.
            return BuildAnalogWriteRequest(pinNumber, value);
        }

        /// <summary>
        ///   Generates a byte array containing the encoded data that represents a request to
        ///   set the I/O mode for the specified digital pin.
        /// </summary>
        /// <param name = "pinNumber">The digital pin number to set.</param>
        /// <param name = "state">The new pin mode.</param>
        /// <returns>The encoded message.</returns>
        /// <remarks>
        ///   The <paramref name = "state" /> parameter can be specified as 
        ///   PinMode.Servo, but this mode is not supported in the  StandardFirmata sketch.
        /// </remarks>
        public static byte[] BuildSetPinModeRequest(int pinNumber, PinMode state)
        {
            return new[]
                       {
                           (byte) RequestMessageType.SetPinMode,
                           (byte) pinNumber,
                           (byte) state
                       };
        }

        /// <summary>
        ///   Generates a byte array containing the encoded data that represents a request to
        ///   set the sampling rate for the Firmata sketch running on the target hardware. 
        ///   The sampling rate is how frequently the target hardware polls for data updates.
        /// </summary>
        /// <param name = "milliseconds">The new sampling rate in milliseconds.</param>
        /// <returns>The encoded message.</returns>
        /// <remarks>
        ///   The StandardFirmata sketch uses a default value of 19 milliseconds.
        /// </remarks>
        public static byte[] BuildSetSamplingIntervalRequest(int milliseconds)
        {
            var data = milliseconds.ToTwo7BitBytes();
            return new[]
                       {
                           START_SYSEX,
                           (byte) RequestMessageType.SetSamplingInterval,
                           data[0],
                           data[1],
                           END_SYSEX
                       };
        }

        /// <summary>
        ///   Generates a byte array containing the encoded data that represents a request to
        ///   reset the stored state on the target device. This does not request a hard reset 
        ///   of the target hardware
        /// </summary>
        /// <returns>The encoded message.</returns>
        public static byte[] BuildSystemResetRequest()
        {
            return new[]
                       {
                           (byte) RequestMessageType.SystemReset
                       };
        }

        #endregion

        #region Private Constants and Static Fields

        private const byte END_SYSEX = 0xF7;
        private const byte START_SYSEX = 0xF0;

        #endregion
    }
}